#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <common/macro/build.h>
#include <common/macro/array.h>
#include <common/macro/debug.h>

#include <storage/fsm.h>

API_LIBLOCAL fsm_t *fsm_new() {
	fsm_t *fsm;

	fsm = malloc(sizeof(*fsm));

	fsm->items = NULL;
	fsm->items_c = 0;

	return fsm;
}

API_LIBLOCAL void fsm_destroy(fsm_t *fsm) {
	if (fsm == NULL) return;

	free(fsm->items);
	free(fsm);
}

API_LIBLOCAL void fsm_flush(fsm_t *fsm) {
	assert(fsm != NULL);

	free(fsm->items);
	fsm->items = NULL;
	fsm->items_c = 0;
}

API_LIBLOCAL inline ssize_t fsm_item_size() {
	return (sizeof(page_size_t) + sizeof(char));
}

API_LIBLOCAL int fsm_serialize_file(fsm_t *fsm, iohandle_t out) {
	char *buf;
	char page_type_char;
	int rv;
	size_t i;

	assert(fsm != NULL);
	assert(out != NULL);

	buf = malloc((sizeof(page_type_char) + sizeof(page_size_t)) * fsm->items_c);
	for (i = 0; i < fsm->items_c; i++) {
		page_type_char = page_type_to_char(fsm->items[i].page_type);
		buf[0 + (i * (sizeof(page_type_char) + sizeof(page_size_t)))] = page_type_char;
		memcpy(&buf[1 + (i * (sizeof(page_type_char) + sizeof(page_size_t)))], &fsm->items[i].page_size, sizeof(page_size_t));
	}

	rv = 0;
	if (io_write(buf, sizeof(page_type_char) + sizeof(page_size_t), fsm->items_c, out) != fsm->items_c) {
		rv = -1;
	}

	free(buf);
	return rv;
}

API_LIBLOCAL fsm_t *fsm_deserialize_file(iohandle_t  in, int max_items) {
	char *buf;
	char page_type_char;
	fsm_t *fsm;
	page_size_t page_size;
	size_t i, dataread;

	assert(in != NULL);

	fsm = fsm_new();

	i = 0;
	buf = malloc((sizeof(page_type_char) + sizeof(page_size)) * max_items);
	memset(buf, 0xDE, (sizeof(page_type_char) + sizeof(page_size)) * max_items);
	dataread = io_read(buf, sizeof(page_type_char) + sizeof(page_size), max_items, in);
	if (dataread <= 0) {
		free(buf);
		return fsm;
	}

	fsm->items_c = dataread;
	fsm->items = malloc(sizeof(*fsm->items) * fsm->items_c);
	while (i < fsm->items_c) {
		page_type_char = buf[0 + (i * (sizeof(page_type_char) + sizeof(page_size)))];
		memcpy(&page_size, &buf[1 + (i * (sizeof(page_type_char) + sizeof(page_size)))], sizeof(page_size));
		debugmsg("Read value: %u -- %hhx %hhx.\n", page_size, buf[1], buf[2]);

		fsm->items[i].page_type = page_char_to_type(page_type_char);
		fsm->items[i].page_size = page_size;
		debugmsg("[%u]", page_size);
		i++;
	}
	debugmsg("\n");

	free(buf);

	return fsm;
}

API_LIBLOCAL int fsm_get_fitting_page(fsm_t *fsm, page_size_t request, enum page_type_e type, page_number_t *page_number, page_number_t *skip_page_number) {
	size_t i;

	assert(fsm != NULL);
	assert(page_number != NULL);

	if (fsm->items == NULL) {
		return -1;
	}

	for (i = 0; i < fsm->items_c; i++) {
		if (skip_page_number != NULL && *skip_page_number == i)
			continue;

		/*
		 * Either we require specific free space in a page a of given type,
		 * or the page is of type PAGE_ANY, in which case it is a released
		 * page and we can do whatever we want with it.
		 */
		if ((fsm->items[i].page_type != PAGE_LEASE) &&
			( (fsm->items[i].page_size >= request && fsm->items[i].page_type == type) ||
			   fsm->items[i].page_type == PAGE_ANY) ) {
			*page_number = i;
			return 0;
		}
	}

	return -1;
}

API_LIBLOCAL void fsm_set_page(fsm_t *fsm, page_number_t page_number, enum page_type_e page_type, page_size_t newsize) {
	assert(fsm != NULL);
	assert(page_number <= fsm->items_c);

	/* This is also why we assert page number <= pages, not just <. */
	if (page_number == fsm->items_c) {
		fsm->items_c += 1;
		fsm->items = realloc(fsm->items, sizeof(*fsm->items) * fsm->items_c);
	}

	fsm->items[page_number].page_size = newsize;
	fsm->items[page_number].page_type = page_type;
}

API_LIBLOCAL inline int fsm_seek_to_page(iohandle_t fp, page_number_t page_number) {
	return io_seek(fp, (sizeof(page_size_t) + 1) * page_number, SEEK_SET);
}
